#include <croco/os/select.hpp>
#include <croco/common_exceptions.hpp>
#include <poll.h>
#include <cerrno>
#include <cassert>
#include <vector>
#include <algorithm>

namespace cros = croco::os;

cros::select_timeout::select_timeout(second ts)
: m_ms(ts.value * 1000)
{
    assert(m_ms >= 0);
}

cros::select_timeout::select_timeout(milisecond tms)
: m_ms(tms.value)
{
    assert(m_ms >= 0);
}

cros::select_fds& cros::select_fds::add_read(native_handle_type handle)
{
    read.insert(handle);
    return *this;
}

cros::select_fds& cros::select_fds::add_write(native_handle_type handle)
{
    write.insert(handle);
    return *this;
}

cros::select_fds& cros::select_fds::add_interrupt(native_handle_type handle)
{
    interrupt.insert(handle);
    return *this;
}

bool cros::select_fds::interrupted(const std::set<native_handle_type>& signaled) const
{
    if(signaled.empty())
    {
        return false;
    }
    if(signaled.size() == 1)
    {
        return interrupt.find(*signaled.begin()) != interrupt.end();
    }
    std::vector<native_handle_type> common;
    std::set_intersection(interrupt.begin(), interrupt.end(), signaled.begin(), signaled.end(), std::back_inserter(common));
    return !common.empty();
}

std::set<cros::native_handle_type> cros::do_select(const select_fds& descriptors, select_timeout timeout)
{
    typedef std::set<native_handle_type>::const_iterator nh_it;
    std::vector<pollfd> input;
    for(nh_it i = descriptors.read.begin(), end = descriptors.read.end(); i != end; ++i)
    {
        input.push_back(pollfd());
        input.back().fd = *i;
        input.back().events = POLLIN | POLLPRI;
    }
    for(nh_it i = descriptors.interrupt.begin(), end = descriptors.interrupt.end(); i != end; ++i)
    {
        input.push_back(pollfd());
        input.back().fd = *i;
        input.back().events = POLLIN | POLLPRI;
    }
    for(nh_it i = descriptors.write.begin(), end = descriptors.write.end(); i != end; ++i)
    {
        input.push_back(pollfd());
        input.back().fd = *i;
        input.back().events = POLLOUT | POLLWRBAND;
    }
    while(true)
    {
        if(poll(&input[0], input.size(), timeout.value()) < 0)
        {
            if(errno != EINTR)
            {
                throw os_error() << boost::errinfo_api_function("poll") << boost::errinfo_errno(errno);
            }
            // EINTR: poll has been interrupted (for instance by signal) not by one of registered descriptors thus is continued
        }
        else
        {
            break;
        }
    }
    std::set<native_handle_type> signaled;
    for(std::vector<pollfd>::const_iterator i = input.begin(), end = input.end(); i != end; ++i)
    {
        if((i->revents & i->events) != 0)
        {
            signaled.insert(i->fd);
        }
    }
    return signaled;
}
